package cmd

import (
	"crypto/md5"
	"fmt"
	"gosync/models"
	"io"
	"log"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/codegangsta/cli"
)

var listenPort, servPort, syncFold string
var CmdServer = cli.Command{
	Name:        "server",
	Usage:       "基于TCP/IP协议的文件传输服务器端",
	Description: "通用传输组件。完成基于TCP/IP协议的文件传输工作。",
	Action:      runServe,
	Flags: []cli.Flag{
		cli.StringFlag{"port", "8000", "server listen port", ""},      //传输端口
		cli.StringFlag{"dir", "/tmp/gosync/", "recive sync fold", ""}, //接收文件夹名称
	},
}

//服务器端开启
func runServe(ctx *cli.Context) {
	listenPort = ctx.String("port")
	syncFold = ctx.String("dir")

	if len(listenPort) == 0 {
		log.Fatal("Missing required --port parameter")
		return
	}

	if len(syncFold) == 0 {
		log.Fatal("Missing required --folder parameter")
		return
	}

	servPort = fmt.Sprintf(":%s", listenPort)
	fmt.Println("Start Service")
	//监听服务端口
	l, err := net.Listen("tcp", servPort)
	if err != nil {
		fmt.Println("net failed:", err)
		return
	}
	//根据配置建立服务端接收用文件夹
	err = os.MkdirAll(syncFold, 0755)
	if err != nil {
		fmt.Println(err)
	}

	for {
		conn, err := l.Accept()
		if err != nil {
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				continue
			}
			fmt.Println("network error", err)
		}
		go Handler(conn)
	}
}

func Handler(conn net.Conn) {
	defer conn.Close()
	state := 0 //数据状态初始化 0：未接收文件 1：开始接收文件 2：文件接收完毕
	var cmd *models.SysFileInfo
	var fSize int64
	var tempFileName string
	var n int64
	for {
		buffer := make([]byte, 2048)  //定义缓冲区大小
		num, err := conn.Read(buffer) //信道开始读取数据
		numLen := int64(num)
		if err != nil && err != io.EOF {
			fmt.Println("cannot read", err)
		}
		n = 0
		if state == 0 {
			n, cmd = cmdParse(buffer[:num]) //解析客户端传输过来的命令
			tempFileName = fmt.Sprintf("%s.newsync", cmd.FName)
			fSize = cmd.FSize
			state = 1
		}
		if state == 1 {
			last := numLen
			if fSize <= numLen-n {
				last = fSize + n
				state = 2
			}
			err = writeToFile(buffer[int(n):int(last)], tempFileName, cmd.FPerm)
			if err != nil {
				fmt.Println("read num error : ", err)
			}
			fSize -= last - n
			if state == 2 {
				//服务器端如果存在同名文件则直接删除
				//Remove removes the named file or directory.
				//If there is an error, it will be of type *PathError.
				os.Remove(cmd.FName)
				//Rename(oldpath,newpath)
				err = os.Rename(tempFileName, cmd.FName)
				if err != nil {
					fmt.Println("rename ", tempFileName, " to ", cmd.FName, " failed")
				}

				//Chtimes changes the access and modification times of the named file,
				//similar to the Unix utime() or utimes() functions.
				err = os.Chtimes(cmd.FName, time.Now(), cmd.FMtime)
				if err != nil {
					fmt.Println("change the mtime error ", err)
				}

				fileHandle, err := os.Open(cmd.FName)
				if err != nil {
					fmt.Println("open ERROR", err)
				}

				h := md5.New() //生成一个MD5码
				io.Copy(h, fileHandle)
				newfMd5 := fmt.Sprintf("%x", h.Sum(nil))
				if newfMd5 == cmd.FMd5 {
					sendInfo := fmt.Sprintf("%s sync success", cmd.FName)
					conn.Write([]byte(sendInfo))
				} else {
					sendInfo := fmt.Sprintf("%s sync failed", cmd.FName)
					conn.Write([]byte(sendInfo))
				}
			}
		}
	}
}

//命令解析操作,在服务器端重组文件结构
func cmdParse(infor []byte) (int64, *models.SysFileInfo) {
	var i int64
	for i = 0; i < int64(len(infor)); i++ {
		if infor[i] == '\n' && infor[i-1] == '\r' {
			cmdLine := strings.Split(string(infor[:i-1]), " ")
			fileName := fmt.Sprintf("%s/%s", syncFold, cmdLine[1])
			filePerm, _ := strconv.Atoi(cmdLine[3])
			fileMtime, _ := strconv.ParseInt(cmdLine[2], 10, 64)
			fileSize, _ := strconv.ParseInt(cmdLine[4], 10, 64)
			fileInfo := &models.SysFileInfo{
				FName:  fileName,
				FMtime: time.Unix(fileMtime, 0),
				FPerm:  os.FileMode(filePerm),
				FSize:  fileSize,
				FMd5:   string(cmdLine[5]),
			}
			return i + 1, fileInfo
		}
	}
	return 0, nil
}

//文件写入操作
func writeToFile(data []byte, fileName string, perm os.FileMode) error {
	writeFile, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND|os.O_CREATE, perm)
	if err != nil {
		fmt.Println("write file error:", err)
		return err
	}
	defer writeFile.Close()
	_, err = writeFile.Write(data)
	if err != nil {
		fmt.Println("write file error", err)
		return err
	}
	return nil
}
